home *** CD-ROM | disk | FTP | other *** search
/ SPACE 1 / SPACE - Library 1 - Volume 1.iso / misc~1 / 123 / stcitdoc.arc / HACK.DOC < prev    next >
Encoding:
Text File  |  1987-09-10  |  19.8 KB  |  453 lines

  1. HACK.DOC
  2.  
  3.           The internals of Citadel -- not
  4.               for the weak-hearted
  5.  
  6.                 OVERVIEW
  7.  
  8.     The fundamental structure of the system is very simple.  CTDLMSG.SYS
  9. is a circular file.  New messages are simply written around the
  10. buffer in an endless circle, overwriting old messages in their way. 
  11. There is no other way of deleting messages, and text is never
  12. shuffled on disk. Messages are numbered consecutively and start with
  13. an FF (hex) byte.  Except for this FF start-of-message byte, all
  14. bytes in the message file have the high bit set to 0.  This means
  15. that in principle it is trivial to scan through the message file and
  16. locate message N if it exists, or return error.  (Complexities, as
  17. usual, crop up when we try for efficiency...)
  18.  
  19.     Each room is basically just a list of message numbers.  Each
  20. time we enter a new message in a room, we slide all the old message-
  21. numbers down a slot, and probably the oldest one falls off the bottom.
  22. Reading a room is just a matter looking up the messages one by one
  23. and printing them out.  If the message has been overwritten already,
  24. we just ignore it.
  25.  
  26.     Implementing the "new message" function is also trivial in
  27. principle: we just keep track, for each caller in the userlog, of
  28. the highest-numbered message which existed on the >last< call.
  29. (Remember, message numbers are simply assigned sequentially each
  30. time a message is created.  This sequence is global to the entire
  31. system, not local within a room.)  If we ignore all message-numbers
  32. in the room less than this, only new messages will be printed.  Voila!
  33. Code up the system described thus far, and you'll have a good
  34. approximation to Version 1.  Better stop and reread everything to
  35. here, so you can pick out the fundamental mechanisms among all of
  36. Version 2's bells and whistles.
  37.  
  38.  
  39. ----------------------------------------------------------------------
  40.  
  41.         message format on disk    (CTDLMSG.SYS)
  42.  
  43.  
  44.     Message format has changed relative to V1, in the direction of
  45. using more disk space and providing greater flexibility.
  46.  
  47.     A message now consists of a sequence of character strings.  Each
  48. string begins with a type byte indicating the meaning of the string
  49. and is ended with a null.  All strings are printable ASCII: in
  50. particular, all numbers are in ASCII rather than binary.  This is
  51. for simplicity, both in implementing the system and in implementing
  52. other code to work with the system.  For instance, a database driven
  53. off Citadel archives can do wildcard matching without worrying about
  54. unpacking binary data such as dates first.  To provide later
  55. downward compatability, all software should be written to IGNORE
  56. fields not currently defined.
  57.  
  58.  
  59.           The type bytes currently defined are:
  60.  
  61. BYTE    Mnemonic    Comments
  62.  
  63. 0xFF            Start-of-message indicator.  Followed by local
  64.             message ID# as ASCII string, null-terminated as
  65.             always.  This byte is the >only< byte which has
  66.             the high bit set in a Citadel message.buf file.
  67.             This field must be present in every message.
  68. A    Author        Name of originator of message.
  69. D    Date        Date message was created.
  70. C    Time        Time message was created.
  71. M    Message     Text of message.  Is last field in a message, by
  72.             definition.  Following data will be ignored.
  73.             This field must be present in every message.
  74. N    Name        Human name for node originated on.  Used on
  75.             title line of foreign messages.  Ex:
  76.             ODD-DATA
  77.             will produce a title message something like
  78.             82Nov23 from Cynbe ru Taren @ODD-DATA
  79. O    Origin        ID of node message originated on: Country code plus
  80.             phone number of system.  (Not stored for locally
  81.             originated messages.)  Ex:
  82.             US 206 633 3282
  83. R    Room        Room of origin.  Topic.
  84. S    Source ID#    Message ID # on system message was created on.
  85.             Two 16-bit integers (high and low halves of
  86.             full 32-bit ID#) separated by a blank.    Ex:
  87.             0 13654
  88. T    To        Addressee.  Used only for private messages in Mail>,
  89.             in version 2.00 .
  90. n            Reserved for future use.
  91.  
  92.             EXAMPLE
  93.  
  94.     Let <FF> be a 0xFF byte, and <0> be a null (0x00) byte.  Then a
  95. message which prints as
  96.  
  97.     LOGLAN> read new
  98.  
  99.     82Nov04 From James Cooke Brown
  100.     Loi uiue i Ti logla
  101.  
  102.     LOGLAN>
  103.  
  104. might be stored as
  105.  
  106. <FF>0 3583<0>D82Nov04<0>AJames Cooke Brown<0>RLOGLAN<0>MLoi uiue i Ti logla<0>
  107. |--Local ID--|---Date---|-----Author---------|--Room---|-------Message--------|
  108.  
  109.     The date, room and author fields could be in any order. Not all
  110. fields are printed by default.  The local ID# and Room field are
  111. suppressed here. An isolated system will not normally have use for
  112. fields beyond those used in this example.
  113.  
  114.     Lines are marked with C NewLine (ASCII LF) characters, within
  115. the message text proper.
  116.  
  117.  
  118.  
  119. ----------------------------------------------------------------------
  120.  
  121.             NETWORKING
  122.  
  123.  
  124.     Citadel nodes network by sharing one or more rooms.  Any Citadel
  125. node can choose to keep an image of any public room on any other
  126. Citadel node (subject to willingness to foot the phone bills, of
  127. course!).  The procedure in essence simply involves calling the
  128. imaged node up periodically and copying any new messages in the
  129. imaged room into the local image.
  130.  
  131.     There is no necessary reciprocity or pre-arrangement, although
  132. convenience and politeness respectively suggest both.  The node
  133. which gets the information foots the phone bill for the transaction.
  134. This promotes simple and harmonious relations between the nodes.
  135.  
  136.     Complexities arise primarily from the possibility of densely
  137. connected networks: one does not wish to accumulate multiple copies
  138. of a given message, which can easily happen.  Nor does one want to
  139. see old messages percolating indefinitely through the system.
  140.  
  141.     This problem is handled by a simple brute-force mechanism: each
  142. node keeps a list of all messages it has seen recently, recording
  143. origin system, creation date, and original ID#.  When downloading,
  144. messages which have already been seen, or which are too old to be
  145. remembered, are skipped.  Messages can percolate outward through a
  146. large network with no global routing or control, but do not
  147. reproduce wildly or cycle indefinitely.
  148.  
  149.  
  150.     The above discussion should make the function of the new fields
  151. reasonably clear:
  152.  
  153.  o  Every message needs a local ID#, so the system can determine if
  154.     a given caller has seen it before.
  155.  o  Travelling messages need to carry system of origin, date of
  156.     origin, and original ID# with them, to keep reproduction and
  157.     cycling under control.
  158.  
  159.  
  160.  
  161. ----------------------------------------------------------------------
  162.  
  163.             PORTABILITY PROBLEMS
  164.  
  165.  
  166.     Check all I/O, modem, console, and file stuff, and especially
  167. the dPrintf and mPrintf functions.
  168.  
  169.  
  170.  
  171. ----------------------------------------------------------------------
  172.  
  173.         "Room" records (CTDLROOM.SYS)
  174.  
  175.  
  176.     The rooms are basically indices into ctdlMsg.sys, the message
  177. file. As noted in the overview, each is essentially an array of
  178. pointers into the message file.  The pointers consist of a 16-bit
  179. message ID number (we will wrap around at 64K for these purposes)
  180. together with a 16-bit psuedo-sector offset within ctdlMsg.sys
  181. telling us where the message begins.
  182.  
  183.     Since messages are number sequentially and written circularly,
  184. the set of messages existing in ctdlMsg.sys will always form a
  185. continuous sequence at any given time.  Thus, by remembering the ID
  186. numbers of the oldest and newest messages in the message file, we
  187. can check to see if a message exists >before< going to disk, saving
  188. ourselves (and the disk drive) the pain of futile seeks in search of
  189. the nonexistent. This information is recorded in oldest and newest,
  190. 32 bit numbers. You'll be seeing more of these...
  191.  
  192.     The newest is simply incremented each time we enter a new
  193. message in the message files.  Oldest is incremented each time we
  194. overwrite an FF (start-of-message) byte in the course of writing a
  195. new message into the files.  This corresponds to dead reckoning --
  196. current code never checks to see that the message number of the
  197. message we are overwriting is what we think it is.  In a garbaged
  198. file with extra FF bytes around, this could cause oldest to count
  199. too rapidly, eventually perhaps overtaking newest, at which time the
  200. system will look completely empty.  If you suspect something like
  201. this is going on, just use configur.exe to rebuild accurate numbers.
  202.  
  203. That should be enough background to tackle a full-scale room.  From
  204. ctdl.h :
  205.  
  206. struct {
  207.     unsigned char rbgen;    /* generation number of room        */
  208.     struct rflags rbflags;    /* same bits as flags above        */
  209.     char    rbname[NAMESIZE];    /* name of this room            */
  210.     pathSpec rbdirname;        /* user directory for room's files    */
  211.     struct {
  212.     ulong     rbmsgNo;    /* every message gets unique#        */
  213.     ulong     rbmsgLoc;    /* sector message starts in        */
  214.     } msg[MSGSPERRM];
  215. } roomBuf;
  216.  
  217. [Note that all components start with "rb" for roomBuf, to make sure
  218.  we don't accidentally use an offset in the wrong structure. Be very
  219.  careful also to get a meaningful sequence of components -- C86
  220.  provides no checking on this sort of stuff either.]
  221.  
  222.     Rbgen handles the problem of rooms which have died and been
  223. reborn under another name.  This will be clearer when we get to the
  224. log file. For now, just note that each room has a generation number
  225. which is bumped by one each time it is recycled.
  226.  
  227.     Rbflags is just a bag of flags recording the status of the room.
  228. The defined flags are:
  229.  
  230.     INUSE.    TRUE if the room is valid, FALSE if it is free for
  231.         re-assignment.
  232.     PUBLIC.    TRUE if the room is visible by default.
  233.     ISDIR.    TRUE if the room is a window onto some disk/userspace.
  234.     PERMROOM.    TRUE if the room should not be recycled even if empty.
  235.     (Lobby>, Mail> and all ISDIRs are automatically permanent.)
  236.  
  237.  
  238.     Rbname is just an ASCII string (null-terminated, like all strings)
  239. giving the name of the room.
  240.  
  241.     Rbdirname is meaningful only in ISDIR rooms, in which case it
  242. gives the full pathname of the directory to window (in the form
  243. drive:path.)
  244.  
  245.     Finally, msg is the array of pointers into the message file. 
  246. RbmsgNo is the ID number of the message, and RbmsgLoc is the sector
  247. it begins in.  (For NIL, we stick the value -1 in RbmsgLoc.)
  248.  
  249.  
  250.     RoomTab is just a little index into ctdlRoom.sys, to keep us
  251. from constantly pounding around on the disk looking for things.  It
  252. basically records the name and status of each room.  It is 100%
  253. redundant with the file itself... as all our indices are.  (As all
  254. indices should be!) Note that RoomTab is a significant consumer of
  255. RAM all by itself.  It is RAM well spent, but if you have to shave
  256. Citadel a few K to make it fit your system, cutting the number of
  257. rooms a bit is one try.
  258.  
  259. The only field new to us in roomTab is rtlastMessage, recording the
  260. most recent message in the room.  When we are searching for rooms
  261. with messages a given caller hasn't seen, we can check this number
  262. in RAM and avoid unprofitable disk accesses.
  263.  
  264.  
  265. ----------------------------------------------------------------------
  266.  
  267.             log records (CTDLLOG.SYS)
  268.  
  269.  
  270.     This is the fun one.  Get some fresh air and plug in your
  271. thinking cap first.  (Time, space and complexity are the eternal
  272. software rivals. We've got 256 log entries x about 500 messages
  273. spread over up to 128 rooms to worry about, and with floppies disk
  274. access time is important... so perforce, we opt for lots of
  275. complexity to keep time and space in bounds.)
  276.  
  277.     To understand what is happening in the log code takes a little
  278. persistence. You also have to disentangle the different activities
  279. going on and tackle them one by one.
  280.  
  281.  o    We want to remember some random things such as terminal screen
  282.     size, and automatically set them up for each caller at login.
  283.  
  284.  o    We want to be able to locate all new messages, and only new
  285.     messages, efficiently.    Messages should stay new even if it
  286.     takes a caller a couple of calls to get around to them.
  287.  
  288.  o    We want to remember which private rooms a given caller knows
  289.     about, and treat them as normal rooms.    This means mostly
  290.     automatically seeking out those with new messages.  (Obviously,
  291.     we >don't< want to do this for unknown private rooms!)    This
  292.     has to be secure against the periodic recycling of rooms
  293.     between calls.
  294.  
  295.  o    We want to support private mail to a caller.
  296.  
  297.  o    We want to provide some protection of this information (via
  298.     passwords at login) and some assurance that messages are from
  299.     who they purport to be from (within the system -- one shouldn't
  300.     be able to forge messages from established users).
  301.  
  302. Lifting another page from ctdl.h gives us:
  303.  
  304. struct logBuffer {
  305.     unsigned char lbnulls;        /* # nulls to print after newline    */
  306.     struct lflags lbflags;        /* UCMASK, LFMASK, EXPERT        */
  307.     unsigned char lbwidth;        /* terminal width            */
  308.     char    lbname[NAMESIZE];   /* caller's name            */
  309.     char    lbpw[NAMESIZE];     /* caller's password        */
  310.     int     lbgen[MAXROOMS];    /* 5 bits gen, 3 bits lastVisit    */
  311.     ulong    lbvisit[MAXVISIT];  /* newestLo on last few calls    */
  312.     ulong    lbslot[MAILSLOTS];  /* for private mail         */
  313.     ulong    lbId[MAILSLOTS];    /* for private mail         */
  314. }
  315.  
  316. Looks simple enough, doesn't it?  One topic at a time:
  317.  
  318.         RANDOM CONFIGURATION PARAMETERS
  319.  
  320.     These are in the first three fields in the record.  Lbnulls
  321. gives the number of nulls to print after a newline, for folks who
  322. like simultaneous hardcopy.    Or any remaining ASR33 aficionados
  323. calling up... Lbwidth is the caller's screen width.  We format all
  324. messages to this width, as best we can.    Lbflags is another bit-bag,
  325. recording uppercase-only folks, people who need a linefeed after a
  326. carraige-return, people who want to suppress the little automatic
  327. hints all through the system, and people who like to see the time a
  328. message was created.
  329.  
  330.  
  331.              FINDING NEW MESSAGES
  332.  
  333.     This is the most important.  Thus, it winds up being the most
  334. elaborate.  Conceptually, what we would like to do is mark each
  335. message with a bit after our caller has read it, so we can avoid
  336. printing it out again next call.  Unfortunately, with 256 log
  337. entries this would require adding two sectors to each message... and
  338. we'd wind up reading off disk lots of messages which would never get
  339. printed.  So we resort to an arcane mixture of approximation and low
  340. animal cunning.
  341.  
  342.     The approximation comes in doing things at the granularity of
  343. rooms rather than messages.  Messages in a given room are "new"
  344. until we visit it, and "old" after we leave the room... whether we
  345. read any of them or not.  This can actually be defended: anyone who
  346. passes through a room without reading the contents probably just isn't
  347. interested in the topic, and would just as soon not be dragged back
  348. every visit and forced to read them.  Given that messages are
  349. numbered sequentially, we can simply record the most recent message
  350. ID# of each room as of the last time we visited it.  With 128 rooms,
  351. this would give us (for each user) an array of 128 integers, or 256
  352. bytes.
  353.  
  354.     This is still too much -- I'd like the complete log record for a
  355. user to be 256 bytes or less, and we have other stuff to do yet.
  356.  
  357.     So, we complicate a little more.  We record in lbvisit[MAXVISIT]
  358. the most recent message ID# in the system on each of the last six
  359. calls or so.    Now, for each room, we can just indicate which call
  360. we last visited the room on.  This takes 3 bits per room, which we
  361. stash in the low three bits of lbgen[MAXROOMS].    Now we're down to
  362. 128 rooms x 3 bits (plus a few bytes in lbvisit[], of course), which
  363. is quite reasonable.
  364.  
  365.     Putting it all together, we can now compute whether a given room
  366. has new messages for our current caller without going to disk at all:
  367.  
  368.  > We get the lbgen[] entry for the room in question
  369.  > We mask off the lower 3 bits
  370.  > We use the result as an index into lbvisit[], getting the ID number
  371.    of the most recent message in the system as of the last time we
  372.    visited the room.
  373.  > We compare this with roomTab[].rtlastMessage, which tells us
  374.    what the most recent message in the room is currently.
  375.  
  376.  
  377.          REMEMBERING WHICH PRIVATE ROOMS TO VISIT
  378.  
  379.     This looks trivial at first glance -- just record one bit per
  380. room per caller in the log records.  The problem is that rooms get
  381. recycled periodically, and we'd rather not run through 256 log
  382. entries each time we do it.    So we adopt a kludge which should
  383. work 99% of the time.
  384.  
  385.     As previously noted, each room has a generation number, which is
  386. bumped by one each time it is recycled.  As not noted, this
  387. generation number runs from 0 -> 31 (and then wraps around and
  388. starts over).  Thus, these numbers take 5 bits to represent.  By a
  389. miraculous coincidence, we have exactly 5 bits left in the lbgen[]
  390. entries in the log records.    [Anyone familiar with "capability"
  391. pointers will be encountering deja vu here...]
  392.  
  393.     When someone visits a room, we set the generation number in lbgen[]
  394.     equal to that of the room.  This flags the room as being
  395. available. If the room gets recycled, on our next visit the two
  396. generation numbers will no longer match, and the room will no longer
  397. be available -- just the result we're looking for.  (Naturally, if a
  398. room is marked as PUBLIC, all this stuff is irrelevant.)
  399.  
  400.     This leaves only the problem of an accidental matchup between
  401. the two numbers giving someone access to a Forbidden Room.  We can't
  402. eliminate this danger completely, but it can be reduced to
  403. insignificance for most purposes. (Just don't bet megabucks on the
  404. security of this system!) Each time someone logs in, we set all
  405. "wrong" generation numbers to be one less than the actual generation
  406. of the room. This means that the room must be recycled thirty-one
  407. times before an accidental matchup can be achieved.  (We do this for
  408. all rooms, INUSE or dead, public or private, since any of them may
  409. be reincarnated as a Forbidden Room.)
  410.  
  411.     Thus, for someone to accidentally be lead to a Forbidden Room,
  412. they must establish an account on the system, then not call until
  413. some room has been recycle thirty-one to thirty-two times, which
  414. room must be reincarnated as a Forbidden Room, which someone must
  415. now call back (having not scrolled off the userlog in the mean time)
  416. and read new messages.  The last clause is about the only probable
  417. one in the sequence. The danger of this is much less than the danger
  418. that someone will simply guess the name of the room outright...
  419.  
  420.  
  421.              SUPPORTING PRIVATE MAIL
  422.  
  423.     Can one have an elegant kludge?  This must come pretty close.
  424.  
  425.     Private mail is sent and recieved in the Mail> room, which
  426. otherwise behaves pretty much as any other room. To make this work,
  427. we store the actual message pointers in lbslot[] and lbId[] in the
  428. caller's log record, and then copy them into the Mail> room array
  429. whenever we enter the room.  This requires a little fiddling to get
  430. things just right.  We have to update roomTab[MAILROOM].rtlastMessage
  431. at login to reflect the presence or absence of new messages, for
  432. example.  And MakeMessage() has to be kludged to ask for the name of
  433. the recipient of the message whenever a message is entered in Mail>.
  434. But basically it works pretty well, keeping the code and user
  435. interface simple and regular.
  436.  
  437.  
  438.            PASSWORDS AND NAME VALIDATION
  439.  
  440.     LogTab[] indexes CTDLLOG.SYS, giving us a quick way of finding
  441. people. It is basically a chronologically sorted hash table.  We
  442. keep a two-byte hash of the name and password of each caller in RAM.
  443. When someone tries to log in, we just whip through the table in
  444. order looking for matches on the password hash and loading the
  445. matching logfile entry in.  Bogus hits are eliminated by the simple
  446. expedient of refusing to acknowledge a new user who's name or
  447. password hashes on top of an existing user. Computer chauvinism at
  448. it's best...
  449.     
  450.     This makes it difficult to forge messages from an existing user.
  451. (Fine point: nonprinting characters are converted to printing
  452. characters, and leading, trailing, and double blanks are deleted.)
  453.